package logic;

import genetics.Evolver;
import genetics.GenEntity;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Vector;

import cruncher.TetragraphMap;
import cruncher.WordMap;

public class PlayfairKey extends GenEntity {
	private String[] key = null;
	public static String newline = System.getProperty("line.separator");
	
	public static String cipherText = null;
	public static WordMap wordMap = null;
	public static TetragraphMap tetraMap = null;
	public static PlayfairCipher cipher = null;
	
	public static PlayfairKey generateRandomKey(Language lang)
	{
		PlayfairKey rkey = new PlayfairKey();
		rkey.key = lang.getLetters().clone();
		List<String> klist = Arrays.asList(rkey.key);
		Collections.shuffle(klist);
		rkey.key = (String[]) klist.toArray();
		return rkey;
	}
	
	public PlayfairKey()
	{
		key = new String[25];
	}
	
	public PlayfairKey(String word, Language lang)
	{
		key = new String[25];
		int n = 0;
		for(int i = 0; i < word.length(); ++i)
		{
			String letter = word.substring(i, i + 1).toLowerCase();
			boolean fresh = true;
			if(!Character.isLowerCase(letter.charAt(0))) continue;
			if(!lang.isValidLetter(letter)) continue;
			letter = lang.doMappings(letter);
			letter = letter.substring(0, 1);
			for(int j = 0; j < n; ++j)
				if(key[j].equals(letter))
					fresh = false;
			if(fresh)
			{
				if(n > 24) break; // Overflow
				key[n] = letter;
				n++;
			}
		}
		
		for(int i = 0; i < 25; ++i)
		{
			String letter = lang.getLetter(i);
			boolean fresh = true;
			letter = lang.doMappings(letter);
			letter = letter.substring(0, 1);
			for(int j = 0; j < n; ++j)
				if(key[j].equals(letter))
					fresh = false;
			if(fresh)
			{
				if(n > 24) break; // Overflow
				key[n] = letter;
				n++;
			}
		}
	}
	
	public String getLetter(int x, int y)
	{
		if(key == null) return "";
		if(x >= 0 && x < 5 && y >= 0 && y < 5)
			return key[y * 5 + x];
		else
			return null;
	}
	
	public int findLetter(String letter)
	{
		letter = letter.toLowerCase();
		for(int i = 0; i < 25; ++i)
			if(key[i].equals(letter))
				return i;
		return -1;
	}
	
	public String toString()
	{
		if(key == null) return "[Uninitialized]";
		String str = new String();
		for(int y = 0; y < 5; ++y)
		{
			for(int x = 0; x < 5; ++x)
				str += getLetter(x, y) + " ";
			str += newline;
		}
		return str;
	}

	@Override
	public GenEntity[] crossOver(GenEntity other) {
		if(!(other instanceof PlayfairKey)) return null;
		
		/*
		PlayfairKey[] children = new PlayfairKey[2];
		children[0] = new PlayfairKey();
		children[1] = new PlayfairKey();
		int divX = Evolver.getRnd().nextInt(5) + 1;
		int divY = Evolver.getRnd().nextInt(5) + 1;
		
		for(int x = 0; x < 5; ++x)
		{
			for(int y = 0; y < 5; ++y)
			{
				if(x < divX)
				{
					if(y < divY)
					{
						children[0].key[x + y * 5] = this.key[x + y * 5];
						children[1].key[x + y * 5] = pk.key[x + y * 5];
					}
					else
					{
						children[1].key[x + y * 5] = this.key[x + y * 5];
						children[0].key[x + y * 5] = pk.key[x + y * 5];
					}
				}
				else
				{
					if(y < divY)
					{
						children[1].key[x + y * 5] = this.key[x + y * 5];
						children[0].key[x + y * 5] = pk.key[x + y * 5];
					}
					else
					{
						children[0].key[x + y * 5] = this.key[x + y * 5];
						children[1].key[x + y * 5] = pk.key[x + y * 5];
					}
				}
			}
		}
		*/
		GenEntity[] children = new PlayfairKey[2];
		children[0] = this.clone();
		children[1] = other.clone();
		children[0].mutate();
		children[1].mutate();
		//children[0].fix();
		//children[1].fix();
		
		return children;
	}
	
	public void swapRows(int a, int b)
	{
		String tmp;
		for(int y = 0; y < 5; ++y)
		{
			tmp = key[a + y * 5];
			key[a + y * 5] = key[b + y * 5];
			key[b + y * 5] = tmp;
		}
	}
	
	public void swapColumns(int a, int b)
	{
		String tmp;
		for(int x = 0; x < 5; ++x)
		{
			tmp = key[x + a * 5];
			key[x + a * 5] = key[x + b * 5];
			key[x + b * 5] = tmp;
		}
	}
	
	public void fix()
	{
		int dupes = 0;
		for(int i = 0; i < 24; ++i)
		{
			for(int j = i + 1; j < 25; ++j)
			{
				if(key[j].equals(key[i]))
				{
					key[j] = " ";
					dupes++;
				}
			}
		}
		
		//System.out.println("Dupes : " + dupes);
		
		//System.out.println(this);
		
		if(dupes > 0)
		{
			String[] langLetters = cipher.getLanguage().getLetters();
			for(int i = 0; i < 25; ++i)
			{
				if(key[i].equals(" "))
				{
					//System.out.println("Empty field at " + i);
					String nk = "";
					for(int j = 0; j < 25; ++j)
					{
						boolean unique = true;
						//int id = 0;
						for(int k = 0; k < 25; ++k)
						{
							if(langLetters[j].equals(key[k]))
							{
								unique = false;
								//id = j;
								break;
							}
						}
						if(unique)
						{
							nk = langLetters[j];
							break;
						}
					}
					//System.out.println("Filled with : " + nk);
					key[i] = nk;
				}
			}
		}
		//System.out.println(this);
	}

	@Override
	public int fitness() {
		cipher.setKey(this);
		String dec = cipher.decipherString(cipherText);
		//System.out.println(dec);
		//return wordMap.evaluate(dec);
		return tetraMap.evaluate(dec);
	}

	@Override
	public void mutate() {
		int x1, y1, x2, y2;
		
		x1 = Evolver.getRnd().nextInt(5);
		y1 = Evolver.getRnd().nextInt(5);
		do
		{
			x2 = Evolver.getRnd().nextInt(5);
			y2 = Evolver.getRnd().nextInt(5);
		} while(x1 == x2 && y1 == y2);
		
		//System.out.println(this);
		String tmp = key[x1 + y1 * 5];
		key[x1 + y1 * 5] = key[x2 + y2 * 5];
		key[x2 + y2 * 5] = tmp;
		//System.out.println(x1 + "," + y1 + " -> " + x2 + "," + y2);
		//System.out.println(this);
		//System.out.println();
		//System.out.println();
	}

	@Override
	public GenEntity clone() {
		PlayfairKey pk = new PlayfairKey();
		for(int i = 0; i < 25; ++i)
			pk.key[i] = key[i];
		return pk;
	}
	
	public int adapt(int multi)
	{
		Vector<PlayfairKey> keys = new Vector<PlayfairKey>();
		
		//keys.add((PlayfairKey) this.clone());
		for(int i = 0; i < multi; ++i)
		{
			PlayfairKey pk = (PlayfairKey) this.clone();
			pk.mutate();
			keys.add(pk);
		}
		/*
		for(int i = 0; i < multi; ++i)
		{
			PlayfairKey pk = (PlayfairKey) this.clone();
			pk.mutate();
			pk.mutate();
			keys.add(pk);
		}
		
		for(int i = 0; i < multi; ++i)
		{
			PlayfairKey pk = (PlayfairKey) this.clone();
			pk.mutate();
			pk.mutate();
			pk.mutate();
			keys.add(pk);
		}
		*/
		int row1 = Evolver.getRnd().nextInt(5);
		int row2 = row1;
		while(row2 == row1)
			row2 = Evolver.getRnd().nextInt(5);
		PlayfairKey pk = (PlayfairKey) this.clone();
		pk.swapRows(row1, row2);
		keys.add(pk);
		
		int column1 = Evolver.getRnd().nextInt(5);
		int column2 = column1;
		while(column1 == column2)
			column2 = Evolver.getRnd().nextInt(5);
		pk = (PlayfairKey) this.clone();
		pk.swapColumns(column1, column2);
		keys.add(pk);
		
		pk = (PlayfairKey) this.clone();
		for(int x = 0; x < 5; ++x)
		{
			for(int y = 0; y < 5; ++y)
			{
				if(x < y)
				{
					String tmp = pk.key[x + y * 5];
					pk.key[x + y * 5] = pk.key[y + x * 5];
					pk.key[y + x * 5] = tmp;
				}
			}
		}
		keys.add(pk);
		
		/*
		for(int row1 = 0; row1 < 4; ++row1)
		{
			for(int row2 = row1 + 1; row2 < 5; ++row2)
			{
				PlayfairKey pk = (PlayfairKey) this.clone();
				pk.swapRows(row1, row2);
				keys.add(pk);
			}
		}
		
		for(int column1 = 0; column1 < 4; ++column1)
		{
			for(int column2 = column1 + 1; column2 < 5; ++column2)
			{
				PlayfairKey pk = (PlayfairKey) this.clone();
				pk.swapColumns(column1, column1);
				keys.add(pk);
			}
		}
		*/
		
		
		pk = (PlayfairKey)this.clone();
		pk.swapRows(0, 4);
		pk.swapRows(1, 3);
		keys.add(pk);
		
		pk = (PlayfairKey)this.clone();
		pk.swapColumns(0, 4);
		pk.swapColumns(1, 3);
		keys.add(pk);
		
		
		//keys.add(PlayfairKey.generateRandomKey(cipher.getLanguage()));
		
		
		int max = -1;
		PlayfairKey mkey = null;
		for(PlayfairKey k : keys)
		{
			int score = k.fitness();
			if(score > max)
			{
				max = score;
				mkey = k;
			}
		}
		
		
		//PlayfairKey mkey = keys.get(Evolver.getRnd().nextInt(keys.size()));
		
		for(int i = 0; i < 25; ++i)
			key[i] = mkey.key[i];
		
		return max;
		//return mkey.fitness();
	}
	
	private static final int[] value = new int[] { 1,1,1,1,1,1,1,2,2,2,2,2,2,2,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,7,7,7,7,7,8,8,9,9,9,9,10,10,10,10,10,11,11,11,11,12,12,12,12,13,13,14,14,15,15,16,16,16,16,17,17,18,18,19,19,20,21,21,22,23,23,24,25,26,27,28,29,30,31,33,34,37,39,41,43,48,57,60 };
	
	private SolutionListener listener = null;
	public void setSolutionListener(SolutionListener l) { listener = l; }
	
	public void findSolution(String cipherText, int iterations)
	{
		int gMaxScore = -1;
		PlayfairKey gBest = null;
		PlayfairKey nk = (PlayfairKey) this.clone();
		Random rnd = Evolver.getRnd();
		
		for(int z = 0; z < iterations; ++z)
		{
			key = cipher.getLanguage().getLetters().clone();
			List<String> klist = Arrays.asList(key);
			Collections.shuffle(klist);
			key = (String[]) klist.toArray();
			
			cipher.setKey(this);
			String dec = cipher.decipherString(cipherText);
			int score = tetraMap.evaluate(dec);
			if(wordMap != null) score += wordMap.evaluate(dec) * 3;
			int maxScore = score;
			
			boolean better = true;
			
			while(better)
			{
				better = false;
				
				for(int X = 0; X < 24; ++X)
				{
					
					for(int Y = X + 1; Y < 25; ++Y)
					{
						int n = rnd.nextInt(55) + 1;
						int r1, r2;
						
						for(int i = 0; i < 25; ++i)
							nk.key[i] = key[i];
						
						switch(n)
						{
						case 1:	// Transposition
							for(int x = 0; x < 5; ++x)
							{
								for(int y = 0; y < 5; ++y)
								{
									if(x < y)
									{
										String tmp = nk.key[x + y * 5];
										nk.key[x + y * 5] = nk.key[y + x * 5];
										nk.key[y + x * 5] = tmp;
									}
								}
							}
							break;
						case 2:	// Flip V
							nk.swapRows(0, 4);
							nk.swapRows(1, 3);
							break;
						case 3: // Flip H
							nk.swapColumns(0, 4);
							nk.swapColumns(1, 3);
							break;
						case 4:	// Swap row
							r1 = rnd.nextInt(5);
							do r2 = rnd.nextInt(5); while(r2 == r1);
							nk.swapColumns(r1, r2);
							break;
						case 5:	// Swap column
							r1 = rnd.nextInt(5);
							do r2 = rnd.nextInt(5); while(r2 == r1);
							nk.swapRows(r1, r2);
							break;
						default:	// Swap single field
							for(int i = 0; i < 25; ++i)
								nk.key[i] = key[i];
							String tmp = nk.key[X];
							nk.key[X] = nk.key[Y];
							nk.key[Y] = tmp;
							break;
						}
						
						cipher.setKey(nk);
						dec = cipher.decipherString(cipherText);
						score = tetraMap.evaluate(dec);
						if(wordMap != null) score += wordMap.evaluate(dec) * 3;
						
						
						if(score > maxScore - value[rnd.nextInt(100)])
						{
							better = true;
							maxScore = score;
							for(int i = 0; i < 25; ++i)
								key[i] = nk.key[i];
						}
						
						//System.out.println("Score : " + score + "; MaxScore : " + gMaxScore);
						if(score > gMaxScore)
						{
							gBest = (PlayfairKey) nk.clone();
							gMaxScore = score;
							
							if(listener != null) 
								listener.solution(dec, gBest);
							else
								System.out.println("Najbolje do sada (" + gMaxScore + ") : " + dec);
						}
					}
				}
			}
			
			if(listener != null) 
				listener.iteration(z);
			else
				System.out.println("ITERACIJA br. " + z);
		}
		
		for(int i = 0; i < 25; ++i)
			key[i] = gBest.key[i];
		
		if(listener != null) listener.done();
	}
}
